{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "JMio44BDpYtw"
   },
   "source": [
    "<font color=\"red\">注</font>: 使用 tensorboard 可视化需要安装 tensorflow (TensorBoard依赖于tensorflow库，可以任意安装tensorflow的gpu/cpu版本)\n",
    "\n",
    "```shell\n",
    "pip install tensorflow-cpu\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T11:18:49.499051Z",
     "iopub.status.busy": "2025-02-27T11:18:49.498719Z",
     "iopub.status.idle": "2025-02-27T11:18:49.502028Z",
     "shell.execute_reply": "2025-02-27T11:18:49.501509Z",
     "shell.execute_reply.started": "2025-02-27T11:18:49.499026Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ['LANG'] = 'en_US.UTF-8'\n",
    "os.environ['LC_ALL'] = 'en_US.UTF-8'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T07:49:33.225136Z",
     "start_time": "2024-07-22T07:49:29.446561200Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T11:04:45.461134Z",
     "iopub.status.busy": "2025-02-27T11:04:45.460798Z",
     "iopub.status.idle": "2025-02-27T11:04:51.118740Z",
     "shell.execute_reply": "2025-02-27T11:04:51.118197Z",
     "shell.execute_reply.started": "2025-02-27T11:04:45.461107Z"
    },
    "id": "YMWskinNpYtz",
    "outputId": "841d6519-0a89-4563-cfb9-6c0d79361cd7",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T11:05:04.349238Z",
     "iopub.status.busy": "2025-02-27T11:05:04.348782Z",
     "iopub.status.idle": "2025-02-27T11:05:10.465606Z",
     "shell.execute_reply": "2025-02-27T11:05:10.465001Z",
     "shell.execute_reply.started": "2025-02-27T11:05:04.349213Z"
    },
    "id": "olzoueSyqofw",
    "outputId": "afd2617d-aafb-4d94-fbaa-d56fd122f22e",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://mirrors.cloud.aliyuncs.com/pypi/simple\n",
      "Collecting kaggle\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/66/e3/c775e2213bac0ac1f1fd601d1c915d13934c5944cbff153ede6584acab50/kaggle-1.6.17.tar.gz (82 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m82.7/82.7 kB\u001b[0m \u001b[31m2.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25h  Preparing metadata (setup.py) ... \u001b[?25ldone\n",
      "\u001b[?25hRequirement already satisfied: six>=1.10 in /usr/local/lib/python3.10/site-packages (from kaggle) (1.17.0)\n",
      "Requirement already satisfied: certifi>=2023.7.22 in /usr/local/lib/python3.10/site-packages (from kaggle) (2024.12.14)\n",
      "Requirement already satisfied: python-dateutil in /usr/local/lib/python3.10/site-packages (from kaggle) (2.9.0.post0)\n",
      "Requirement already satisfied: requests in /usr/local/lib/python3.10/site-packages (from kaggle) (2.32.3)\n",
      "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/site-packages (from kaggle) (4.67.1)\n",
      "Collecting python-slugify (from kaggle)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl (10 kB)\n",
      "Requirement already satisfied: urllib3 in /usr/local/lib/python3.10/site-packages (from kaggle) (2.3.0)\n",
      "Collecting bleach (from kaggle)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl (163 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m163.4/163.4 kB\u001b[0m \u001b[31m13.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting webencodings (from bleach->kaggle)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl (11 kB)\n",
      "Collecting text-unidecode>=1.3 (from python-slugify->kaggle)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl (78 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m78.2/78.2 kB\u001b[0m \u001b[31m25.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/site-packages (from requests->kaggle) (3.4.1)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/site-packages (from requests->kaggle) (3.10)\n",
      "Building wheels for collected packages: kaggle\n",
      "  Building wheel for kaggle (setup.py) ... \u001b[?25ldone\n",
      "\u001b[?25h  Created wheel for kaggle: filename=kaggle-1.6.17-py3-none-any.whl size=105800 sha256=bbbab6b4a21db2d77f6e03eba2a26446e6c22ebb735b64b14f91539ef22c9dd6\n",
      "  Stored in directory: /root/.cache/pip/wheels/85/59/78/39b001621fad138a41610d830155c8b08d48649b2197e433c7\n",
      "Successfully built kaggle\n",
      "Installing collected packages: webencodings, text-unidecode, python-slugify, bleach, kaggle\n",
      "Successfully installed bleach-6.2.0 kaggle-1.6.17 python-slugify-8.0.4 text-unidecode-1.3 webencodings-0.5.1\n",
      "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
      "\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.0.1\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!pip install kaggle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T11:06:29.149875Z",
     "iopub.status.busy": "2025-02-27T11:06:29.149484Z",
     "iopub.status.idle": "2025-02-27T11:06:29.262807Z",
     "shell.execute_reply": "2025-02-27T11:06:29.262235Z",
     "shell.execute_reply.started": "2025-02-27T11:06:29.149848Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/mnt/workspace\n"
     ]
    }
   ],
   "source": [
    "!pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T02:01:14.100470Z",
     "iopub.status.busy": "2025-01-21T02:01:14.100150Z",
     "iopub.status.idle": "2025-01-21T02:01:14.214430Z",
     "shell.execute_reply": "2025-01-21T02:01:14.213878Z",
     "shell.execute_reply.started": "2025-01-21T02:01:14.100449Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "slothkong\n"
     ]
    }
   ],
   "source": [
    "!rm -r /content/datasets/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T11:20:31.757342Z",
     "iopub.status.busy": "2025-02-27T11:20:31.756962Z",
     "iopub.status.idle": "2025-02-27T11:20:31.760975Z",
     "shell.execute_reply": "2025-02-27T11:20:31.760466Z",
     "shell.execute_reply.started": "2025-02-27T11:20:31.757315Z"
    },
    "id": "L8O3V3yJqbWH",
    "tags": []
   },
   "outputs": [],
   "source": [
    "import json\n",
    "token = {\"username\":\"cskaoyan\",\"key\":\"ff99d9d7ff71704e3e761217ceec03c5\"}\n",
    "with open('/mnt/workspace/kaggle.json', 'w') as file:\n",
    "  json.dump(token, file)#json.dump类似于write"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-27T11:20:33.212057Z",
     "iopub.status.busy": "2025-02-27T11:20:33.211705Z",
     "iopub.status.idle": "2025-02-27T11:20:33.326254Z",
     "shell.execute_reply": "2025-02-27T11:20:33.325670Z",
     "shell.execute_reply.started": "2025-02-27T11:20:33.212032Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"username\": \"cskaoyan\", \"key\": \"ff99d9d7ff71704e3e761217ceec03c5\"}"
     ]
    }
   ],
   "source": [
    "!cat /mnt/workspace/kaggle.json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T11:20:34.397193Z",
     "iopub.status.busy": "2025-02-27T11:20:34.396867Z",
     "iopub.status.idle": "2025-02-27T11:20:35.062222Z",
     "shell.execute_reply": "2025-02-27T11:20:35.061615Z",
     "shell.execute_reply.started": "2025-02-27T11:20:34.397169Z"
    },
    "id": "Pf8ZxoKzqgzu",
    "outputId": "0face2c5-be84-4ed2-f295-70cd2401708f",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "- path is now set to: /content\n"
     ]
    }
   ],
   "source": [
    "!mkdir -p ~/.kaggle\n",
    "!cp /mnt/workspace/kaggle.json ~/.kaggle/\n",
    "!chmod 600 ~/.kaggle/kaggle.json\n",
    "!kaggle config set -n path -v /content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T11:20:35.974408Z",
     "iopub.status.busy": "2025-02-27T11:20:35.974067Z",
     "iopub.status.idle": "2025-02-27T11:28:34.792278Z",
     "shell.execute_reply": "2025-02-27T11:28:34.791700Z",
     "shell.execute_reply.started": "2025-02-27T11:20:35.974382Z"
    },
    "id": "B8hQi6ejqkAG",
    "outputId": "774e8927-4da0-492d-d7c1-cc39c774dfe3",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dataset URL: https://www.kaggle.com/datasets/slothkong/10-monkey-species\n",
      "License(s): CC0-1.0\n",
      "Downloading 10-monkey-species.zip to /content/datasets/slothkong/10-monkey-species\n",
      "100%|███████████████████████████████████████▉| 547M/547M [07:56<00:00, 1.24MB/s]\n",
      "100%|████████████████████████████████████████| 547M/547M [07:56<00:00, 1.20MB/s]\n"
     ]
    }
   ],
   "source": [
    "!kaggle datasets download -d slothkong/10-monkey-species"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:14:45.310215Z",
     "iopub.status.busy": "2025-02-27T12:14:45.309848Z",
     "iopub.status.idle": "2025-02-27T12:14:48.965773Z",
     "shell.execute_reply": "2025-02-27T12:14:48.965088Z",
     "shell.execute_reply.started": "2025-02-27T12:14:45.310189Z"
    },
    "id": "tporD6JYsFQ-",
    "outputId": "c264f15e-db74-4cd8-f68f-c347c83b438b",
    "tags": []
   },
   "outputs": [],
   "source": [
    "!unzip -o -d /content /content/datasets/slothkong/10-monkey-species/10-monkey-species.zip >/dev/null"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "NPVrMdFjpYt1"
   },
   "source": [
    "## 数据准备\n",
    "\n",
    "```shell\n",
    "$ tree -L 2 archive\n",
    "archive\n",
    "├── monkey_labels.txt\n",
    "├── training\n",
    "│   ├── n0\n",
    "│   ├── n1\n",
    "│   ├── n2\n",
    "│   ├── n3\n",
    "│   ├── n4\n",
    "│   ├── n5\n",
    "│   ├── n6\n",
    "│   ├── n7\n",
    "│   ├── n8\n",
    "│   └── n9\n",
    "└── validation\n",
    "    ├── n0\n",
    "    ├── n1\n",
    "    ├── n2\n",
    "    ├── n3\n",
    "    ├── n4\n",
    "    ├── n5\n",
    "    ├── n6\n",
    "    ├── n7\n",
    "    ├── n8\n",
    "    └── n9\n",
    "\n",
    "22 directories, 1 file\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-27T12:16:44.150234Z",
     "iopub.status.busy": "2025-02-27T12:16:44.149842Z",
     "iopub.status.idle": "2025-02-27T12:16:44.265983Z",
     "shell.execute_reply": "2025-02-27T12:16:44.265425Z",
     "shell.execute_reply.started": "2025-02-27T12:16:44.150206Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "04_10_monkeys_model_1_aliyun.ipynb  kaggle.json\n"
     ]
    }
   ],
   "source": [
    "!ls"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:52:17.543980900Z",
     "start_time": "2024-07-22T08:52:17.520817400Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:16:56.494120Z",
     "iopub.status.busy": "2025-02-27T12:16:56.493749Z",
     "iopub.status.idle": "2025-02-27T12:16:58.699549Z",
     "shell.execute_reply": "2025-02-27T12:16:58.699009Z",
     "shell.execute_reply.started": "2025-02-27T12:16:56.494095Z"
    },
    "id": "nnayCtXTpYt2",
    "outputId": "0055a8d0-ec63-43bd-c02c-378b3e571921",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "load 1097 images from training dataset\n",
      "load 272 images from validation dataset\n"
     ]
    }
   ],
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor, Resize, Compose, ConvertImageDtype, Normalize\n",
    "\n",
    "\n",
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR1 = Path(\"/content/training/\")\n",
    "DATA_DIR2 = Path(\"/content/validation/\")\n",
    "\n",
    "class MonkeyDataset(datasets.ImageFolder):\n",
    "    def __init__(self, mode, transform=None):\n",
    "        if mode == \"train\":\n",
    "            root = DATA_DIR1 / \"training\"\n",
    "        elif mode == \"val\":\n",
    "            root = DATA_DIR2 / \"validation\"\n",
    "        else:\n",
    "            raise ValueError(\"mode should be one of the following: train, val, but got {}\".format(mode))\n",
    "        super().__init__(root, transform) # 调用父类init方法\n",
    "        self.imgs = self.samples # self.samples里边是图片路径及标签 [(path, label), (path, label),...]\n",
    "        self.targets = [s[1] for s in self.samples] # 标签取出来\n",
    "\n",
    "# 预先设定的图片尺寸\n",
    "img_h, img_w = 128, 128\n",
    "transform = Compose([\n",
    "    Resize((img_h, img_w)), # 图片缩放\n",
    "    ToTensor(),\n",
    "    # 预先统计的\n",
    "    Normalize([0.4363, 0.4328, 0.3291], [0.2085, 0.2032, 0.1988]),\n",
    "    ConvertImageDtype(torch.float), # 转换为float类型\n",
    "]) #数据预处理\n",
    "\n",
    "\n",
    "train_ds = MonkeyDataset(\"train\", transform=transform)\n",
    "val_ds = MonkeyDataset(\"val\", transform=transform)\n",
    "\n",
    "print(\"load {} images from training dataset\".format(len(train_ds)))\n",
    "print(\"load {} images from validation dataset\".format(len(val_ds)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:15:15.286096100Z",
     "start_time": "2024-07-22T08:15:15.272778500Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:17:58.997742Z",
     "iopub.status.busy": "2025-02-27T12:17:58.997242Z",
     "iopub.status.idle": "2025-02-27T12:17:59.002391Z",
     "shell.execute_reply": "2025-02-27T12:17:59.001793Z",
     "shell.execute_reply.started": "2025-02-27T12:17:58.997714Z"
    },
    "id": "oS2jYFvMpYt3",
    "outputId": "837dd61e-a898-4fa8-9d5d-51adcf2b2b47",
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9']"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 数据类别\n",
    "train_ds.classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:15:24.968412700Z",
     "start_time": "2024-07-22T08:15:24.960415300Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:02.712675Z",
     "iopub.status.busy": "2025-02-27T12:18:02.712312Z",
     "iopub.status.idle": "2025-02-27T12:18:02.716759Z",
     "shell.execute_reply": "2025-02-27T12:18:02.716218Z",
     "shell.execute_reply.started": "2025-02-27T12:18:02.712641Z"
    },
    "id": "8SJAYnQopYt4",
    "outputId": "15f39953-ca91-40e4-d084-e5e8c1d4667c",
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'n0': 0,\n",
       " 'n1': 1,\n",
       " 'n2': 2,\n",
       " 'n3': 3,\n",
       " 'n4': 4,\n",
       " 'n5': 5,\n",
       " 'n6': 6,\n",
       " 'n7': 7,\n",
       " 'n8': 8,\n",
       " 'n9': 9}"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds.class_to_idx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:22:41.089921600Z",
     "start_time": "2024-07-22T08:22:41.076096400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:03.396266Z",
     "iopub.status.busy": "2025-02-27T12:18:03.395976Z",
     "iopub.status.idle": "2025-02-27T12:18:03.454185Z",
     "shell.execute_reply": "2025-02-27T12:18:03.453687Z",
     "shell.execute_reply.started": "2025-02-27T12:18:03.396244Z"
    },
    "id": "o2x2L3yTpYt4",
    "outputId": "6925790a-4125-4882-9d51-d015fbd6e7eb",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/content/training/training/n0/n0018.jpg 0\n",
      "tensor([[[-0.1553, -0.0236,  0.3525,  ...,  1.1425,  1.8384,  0.9920],\n",
      "         [-0.1365, -0.1365,  0.1833,  ...,  0.8416,  1.9324,  0.9356],\n",
      "         [-0.1177, -0.1741,  0.1268,  ...,  1.1425,  1.3306,  1.3494],\n",
      "         ...,\n",
      "         [ 2.1205,  2.1205,  2.0641,  ...,  0.9920,  0.8416,  0.3713],\n",
      "         [ 2.0641,  1.9136,  1.6691,  ...,  0.3713, -0.0612, -0.0048],\n",
      "         [ 1.9513,  1.6691,  1.2553,  ...,  0.0328, -0.4938, -0.1365]],\n",
      "\n",
      "        [[-0.2193, -0.0456,  0.3983,  ...,  1.3632,  2.0580,  1.0737],\n",
      "         [-0.2386, -0.2000,  0.2053,  ...,  0.9386,  2.1545,  1.1702],\n",
      "         [-0.2772, -0.2772,  0.0702,  ...,  1.1702,  1.5176,  1.6527],\n",
      "         ...,\n",
      "         [ 2.4054,  2.3668,  2.2703,  ...,  0.6491,  0.5333,  0.1088],\n",
      "         [ 2.2896,  2.1352,  1.8650,  ...,  0.0509, -0.3351, -0.3544],\n",
      "         [ 2.1352,  1.8457,  1.4211,  ..., -0.3544, -0.8369, -0.5667]],\n",
      "\n",
      "        [[-0.7283, -0.4324,  0.2975,  ...,  0.2383,  0.9287, -0.3141],\n",
      "         [-0.7678, -0.6297,  0.0805,  ...,  0.1002,  1.1851, -0.3338],\n",
      "         [-0.8467, -0.7480, -0.0971,  ...,  0.3961,  0.8103,  0.6131],\n",
      "         ...,\n",
      "         [-0.4324, -0.4916, -0.6099,  ...,  0.5934,  0.4553,  0.0213],\n",
      "         [-0.5113, -0.6099, -0.8467,  ..., -0.0379, -0.5508, -0.5508],\n",
      "         [-0.6494, -0.8467, -1.1228,  ..., -0.5705, -1.2215, -0.9650]]]) 0\n"
     ]
    }
   ],
   "source": [
    "# 图片路径 及 标签\n",
    "for fpath, label in train_ds.imgs:\n",
    "    print(fpath, label)\n",
    "    break\n",
    "\n",
    "#这个和之前的dataset完全一致\n",
    "for img, label in train_ds:\n",
    "    # c, h, w  label\n",
    "    print(img, label)\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:44:58.447431700Z",
     "start_time": "2024-07-22T08:44:29.539230100Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:03.866915Z",
     "iopub.status.busy": "2025-02-27T12:18:03.866531Z",
     "iopub.status.idle": "2025-02-27T12:18:20.503996Z",
     "shell.execute_reply": "2025-02-27T12:18:20.503218Z",
     "shell.execute_reply.started": "2025-02-27T12:18:03.866893Z"
    },
    "id": "mVb-3y68pYt5",
    "outputId": "3116d220-7fb4-4b1d-d648-baed26152c4f",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.0002]), tensor([0.9999]))\n"
     ]
    }
   ],
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[0:1, :, :].mean(dim=(1, 2))\n",
    "        std += img[0:1, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:32:54.269143900Z",
     "start_time": "2024-07-22T08:32:25.590376800Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:20.505448Z",
     "iopub.status.busy": "2025-02-27T12:18:20.505026Z",
     "iopub.status.idle": "2025-02-27T12:18:37.333348Z",
     "shell.execute_reply": "2025-02-27T12:18:37.332680Z",
     "shell.execute_reply.started": "2025-02-27T12:18:20.505427Z"
    },
    "id": "ARxVczNhpYt5",
    "outputId": "b2b2706e-4c8d-494d-f317-195bc1b55ddc",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([3.6267e-05]), tensor([0.9999]))\n"
     ]
    }
   ],
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[1:2, :, :].mean(dim=(1, 2))\n",
    "        std += img[1:2, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:33:54.095864500Z",
     "start_time": "2024-07-22T08:33:26.014587300Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:37.334263Z",
     "iopub.status.busy": "2025-02-27T12:18:37.334046Z",
     "iopub.status.idle": "2025-02-27T12:18:53.954835Z",
     "shell.execute_reply": "2025-02-27T12:18:53.954313Z",
     "shell.execute_reply.started": "2025-02-27T12:18:37.334240Z"
    },
    "id": "kdtwsOxIpYt5",
    "outputId": "e723ef22-4061-4795-e9c1-4c85bcb2df67",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([-6.5391e-07]), tensor([1.0002]))\n"
     ]
    }
   ],
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[2:3, :, :].mean(dim=(1, 2))\n",
    "        std += img[2:3, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:39:44.080468500Z",
     "start_time": "2024-07-22T08:39:15.435622600Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:53.956649Z",
     "iopub.status.busy": "2025-02-27T12:18:53.956279Z",
     "iopub.status.idle": "2025-02-27T12:18:53.960088Z",
     "shell.execute_reply": "2025-02-27T12:18:53.959614Z",
     "shell.execute_reply.started": "2025-02-27T12:18:53.956627Z"
    },
    "id": "2U0E5JMTpYt6",
    "outputId": "ab841753-c1d0-4dfd-cd6d-375ac31e02d6",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2)) #dim=(1, 2)表示计算均值后，宽和高消除（把宽和高所有的像素加起来，再除以总数）\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:52:57.696309200Z",
     "start_time": "2024-07-22T08:52:57.686315700Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:53.961052Z",
     "iopub.status.busy": "2025-02-27T12:18:53.960649Z",
     "iopub.status.idle": "2025-02-27T12:18:53.964254Z",
     "shell.execute_reply": "2025-02-27T12:18:53.963787Z",
     "shell.execute_reply.started": "2025-02-27T12:18:53.961026Z"
    },
    "id": "vzPl-CPOpYt6",
    "outputId": "51436c68-37f1-42c0-ad44-a5582069bcc1",
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "from torch.utils.data.dataloader import DataLoader\n",
    "\n",
    "batch_size = 64\n",
    "# 从数据集到dataloader\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)\n",
    "val_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False, num_workers=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:53:04.933884100Z",
     "start_time": "2024-07-22T08:52:59.755852600Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:53.965022Z",
     "iopub.status.busy": "2025-02-27T12:18:53.964855Z",
     "iopub.status.idle": "2025-02-27T12:18:56.262956Z",
     "shell.execute_reply": "2025-02-27T12:18:56.262346Z",
     "shell.execute_reply.started": "2025-02-27T12:18:53.965003Z"
    },
    "id": "PvzJPmCKpYt7",
    "outputId": "14c12450-f807-4fc7-f521-fba2d5427f88",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([64, 3, 128, 128])\n",
      "torch.Size([64])\n"
     ]
    }
   ],
   "source": [
    "for imgs, labels in train_loader:\n",
    "    print(imgs.shape)\n",
    "    print(labels.shape)\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "MxR9Zm8FpYt7"
   },
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:56.264040Z",
     "iopub.status.busy": "2025-02-27T12:18:56.263744Z",
     "iopub.status.idle": "2025-02-27T12:18:56.337645Z",
     "shell.execute_reply": "2025-02-27T12:18:56.337158Z",
     "shell.execute_reply.started": "2025-02-27T12:18:56.264016Z"
    },
    "id": "93Z27pjxpYt7",
    "outputId": "5795e337-36fb-4dc0-b9c5-9c94061d524f",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conv1.weight\tparamerters num: 864\n",
      "conv1.bias\tparamerters num: 32\n",
      "conv2.weight\tparamerters num: 9216\n",
      "conv2.bias\tparamerters num: 32\n",
      "conv3.weight\tparamerters num: 18432\n",
      "conv3.bias\tparamerters num: 64\n",
      "conv4.weight\tparamerters num: 36864\n",
      "conv4.bias\tparamerters num: 64\n",
      "conv5.weight\tparamerters num: 73728\n",
      "conv5.bias\tparamerters num: 128\n",
      "conv6.weight\tparamerters num: 147456\n",
      "conv6.bias\tparamerters num: 128\n",
      "fc1.weight\tparamerters num: 4194304\n",
      "fc1.bias\tparamerters num: 128\n",
      "fc2.weight\tparamerters num: 1280\n",
      "fc2.bias\tparamerters num: 10\n"
     ]
    }
   ],
   "source": [
    "\n",
    "class CNN(nn.Module):\n",
    "    def __init__(self, num_classes=10, activation=\"relu\"):\n",
    "        super(CNN, self).__init__()\n",
    "        self.activation = F.relu if activation == \"relu\" else F.selu\n",
    "        self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=\"same\")\n",
    "        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=\"same\")\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=\"same\")\n",
    "        self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=\"same\")\n",
    "        self.conv5 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=\"same\")\n",
    "        self.conv6 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=\"same\")\n",
    "        self.flatten = nn.Flatten()\n",
    "        # input shape is (3, 128, 128) so the flatten output shape is 128 * 16 * 16\n",
    "        self.fc1 = nn.Linear(128 * 16 * 16, 128)\n",
    "        self.fc2 = nn.Linear(128, num_classes)\n",
    "\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x):\n",
    "        act = self.activation\n",
    "        x = self.pool(act(self.conv2(act(self.conv1(x)))))\n",
    "        x = self.pool(act(self.conv4(act(self.conv3(x)))))\n",
    "        x = self.pool(act(self.conv6(act(self.conv5(x)))))\n",
    "        x = self.flatten(x)\n",
    "        x = act(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return x\n",
    "\n",
    "\n",
    "for idx, (key, value) in enumerate(CNN().named_parameters()):\n",
    "    print(f\"{key}\\tparamerters num: {np.prod(value.shape)}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1qNGeGLmpYt8"
   },
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:56.338661Z",
     "iopub.status.busy": "2025-02-27T12:18:56.338248Z",
     "iopub.status.idle": "2025-02-27T12:18:56.441454Z",
     "shell.execute_reply": "2025-02-27T12:18:56.440950Z",
     "shell.execute_reply.started": "2025-02-27T12:18:56.338640Z"
    },
    "id": "mz4gW8hupYt8",
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "vK16Ii8KpYt8"
   },
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:56.442597Z",
     "iopub.status.busy": "2025-02-27T12:18:56.442061Z",
     "iopub.status.idle": "2025-02-27T12:18:56.634788Z",
     "shell.execute_reply": "2025-02-27T12:18:56.634260Z",
     "shell.execute_reply.started": "2025-02-27T12:18:56.442574Z"
    },
    "id": "0RaJusY7pYt9",
    "tags": []
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "\n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\",\n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "\n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "\n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "\n",
    "        )\n",
    "\n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "RhHZoT43pYt-"
   },
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:56.637091Z",
     "iopub.status.busy": "2025-02-27T12:18:56.636605Z",
     "iopub.status.idle": "2025-02-27T12:18:56.642033Z",
     "shell.execute_reply": "2025-02-27T12:18:56.641551Z",
     "shell.execute_reply.started": "2025-02-27T12:18:56.637067Z"
    },
    "id": "X6-gIb2YpYt_",
    "tags": []
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch.\n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "\n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BtnJ4AlcpYt_"
   },
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:56.643256Z",
     "iopub.status.busy": "2025-02-27T12:18:56.642909Z",
     "iopub.status.idle": "2025-02-27T12:18:56.648281Z",
     "shell.execute_reply": "2025-02-27T12:18:56.647687Z",
     "shell.execute_reply.started": "2025-02-27T12:18:56.643225Z"
    },
    "id": "qLSAWIq8pYuA",
    "tags": []
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute\n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter\n",
    "            self.counter = 0\n",
    "        else:\n",
    "            self.counter += 1\n",
    "\n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "colab": {
     "referenced_widgets": [
      "481dddc81fe84be3afc1853a2c537f47"
     ]
    },
    "execution": {
     "iopub.execute_input": "2025-02-27T12:18:56.649451Z",
     "iopub.status.busy": "2025-02-27T12:18:56.649201Z",
     "iopub.status.idle": "2025-02-27T12:20:39.156350Z",
     "shell.execute_reply": "2025-02-27T12:20:39.155767Z",
     "shell.execute_reply.started": "2025-02-27T12:18:56.649423Z"
    },
    "id": "p9T_O6X7pYuA",
    "outputId": "4cd378c8-3792-4cf4-91b4-33d3cbe1e02b",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 80%|████████  | 288/360 [01:38<00:18,  3.89it/s, epoch=15]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 16 / global_step 288\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 80%|████████  | 288/360 [01:42<00:25,  2.82it/s, epoch=15]\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step,\n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "\n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 20\n",
    "\n",
    "activation = \"relu\"\n",
    "model = CNN(num_classes=10, activation=activation)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 adam\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001, eps=1e-7)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/monkeys-cnn-{activation}\")\n",
    "tensorboard_callback.draw_model(model, [1, 3, img_h, img_w])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/monkeys-cnn-{activation}\", save_step=len(train_loader), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=tensorboard_callback,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-27T12:20:39.157378Z",
     "iopub.status.busy": "2025-02-27T12:20:39.157100Z",
     "iopub.status.idle": "2025-02-27T12:20:39.380155Z",
     "shell.execute_reply": "2025-02-27T12:20:39.379623Z",
     "shell.execute_reply.started": "2025-02-27T12:20:39.157347Z"
    },
    "id": "m_D2srP_pYuC",
    "outputId": "2f735bb6-bd73-43e1-afb8-2bd472e33d0c",
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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XmZlZVVq01zzGzlypLJmzRrl1KlTyrFjx5QxY8YoHTt2VPLy8sxtavpZLC0tVXr27KmEh4crR48eVbZs2aK4u7srCxYssERKldQmvyFDhiizZs2q9P5dv37dfL655vfdd98pP/zwg3Lu3DklNjZWefnllxW9Xq+cOnVKURTrft+sjVr6JjX1S4oifZP0Tc3zd5ya+yVFaV59k6qLogEDBiizZ882Py4rK1PatWunLFu2zIJR1c+iRYuU4ODgKs9lZ2crer1e+frrr83Hzpw5owBKVFRUE0VYP7/9xWwymRRvb2/l7bffNh/Lzs5WbG1tlS+++EJRFEU5ffq0AiiHDh0yt/nxxx8VjUajXLlypclir43qOp5x48ZV+xxryi8tLU0BlF27dimKUrufxS1btiharVZJSUkxt/nwww8VFxcXpbi4uGkTqMFv81OU8s7nueeeq/Y51pRf69atlY8++kh171tzp5a+Sa39kqJI31QVa8pPzX2T2vslRbFc36Ta4XMlJSUcPnyY8PBw8zGtVkt4eDhRUVEWjKz+zp8/T7t27ejcuTNTp04lKSkJgMOHD2M0Givl2q1bNzp27Gh1ucbHx5OSklIpl1atWhEaGmrOJSoqCldXV/r162duEx4ejlarJTo6usljro/IyEg8PT0JCAjgmWeeITMz03zOmvK7fv06AG5ubkDtfhajoqIICgrCy8vL3GbkyJHk5OTwyy+/NGH0NfttfhU+//xz3N3d6dmzJwsWLKCgoMB8zhryKysrY/369eTn5xMWFqa69605U1vf1BL6JZC+CawrPzX3TWrtl8DyfZNNw6TR/GRkZFBWVlbpmwTg5eXF2bNnLRRV/YWGhrJ27VoCAgJITk5myZIl3HvvvZw6dYqUlBQMBgOurq6VnuPl5UVKSoplAq6niniret8qzqWkpODp6VnpvI2NDW5ublaR76hRo5gwYQKdOnXiwoULvPzyy4wePZqoqCh0Op3V5GcymXj++ecZNGgQPXv2BKjVz2JKSkqV72/FueaiqvwApkyZgq+vL+3atePEiRO8+OKLxMbGsnHjRqB553fy5EnCwsIoKirCycmJTZs20b17d44dO6aa9625U1Pf1FL6JZC+Sfqm5pGfGvslaD59k2qLIrUZPXq0+d+9evUiNDQUX19fvvrqK+zt7S0Ymairxx57zPzvoKAgevXq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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "l9N2QrfFpYuD"
   },
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-27T12:20:39.381279Z",
     "iopub.status.busy": "2025-02-27T12:20:39.380814Z",
     "iopub.status.idle": "2025-02-27T12:20:40.938818Z",
     "shell.execute_reply": "2025-02-27T12:20:40.938202Z",
     "shell.execute_reply.started": "2025-02-27T12:20:39.381257Z"
    },
    "id": "qiN3FDcJpYuE",
    "outputId": "9c9b50e4-79d5-4027-824b-8f7ef233eea9",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_302/2960646440.py:4: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(f\"checkpoints/monkeys-cnn-{activation}/best.ckpt\", map_location=\"cpu\"))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     2.3352\n",
      "accuracy: 0.6213\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(f\"checkpoints/monkeys-cnn-{activation}/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
